`include "fft_core_h.v"

module fft_core_datapath
#(
  parameter DATA_SIZE  = 32,
  parameter DFT_POINTS = 8
)(
  //OUTPUTS
  output [DATA_SIZE - 1 :0] bus_rd,
  output addr_unit_rdy,
  output mem_rdy,
  output bus_rdy,
  output butt_rdy,
  output interrupt,
  output start,
  //INPUTS
  input [DATA_SIZE - 1 :0] bus_wr,
  input mem_src,
  input busy_n,
  input read_en,
  input write_en,
  input [1:0]bus_addr,
  input mem_rd_core,
  input mem_wr_core,
  input addr_unit_en,
  input start_set,
  input int_set,
  input config_en,
  input config_src,
  input start_butt,
  input enable,
  input clk,
  input rst_n
);
`include "fft_functions.v"

localparam ADDR_SIZE    = clogb2(DFT_POINTS/2);
localparam BUS_ADDR_MSB = clogb2(DFT_POINTS) - 1;

//Memory Map of Core
localparam MEM_READ   = `MEM_READ;//2'b00;
localparam MEM_WRITE  = `MEM_WRITE;//2'b01;
localparam CONFIG_REG = `CONFIG_REG;//2'b10;

//Configuration Bits
localparam INT_MASK  = `INT_MASK;
localparam INT_BIT   = `INT_BIT;
localparam START_BIT = `START_BIT;
localparam MODE_BIT  = `MODE_BIT;
localparam ORDER_BIT = `ORDER_BIT;

wire [DATA_SIZE - 1:0] data_in_bk0, data_out_bk0;
wire [DATA_SIZE - 1:0] data_in_bk1, data_out_bk1;
wire [ADDR_SIZE - 1:0] addr_bk0, addr_bk1;
wire bank0_en, bank1_en;

wire [DATA_SIZE  - 1:0] x_butt_out, y_butt_out;
wire [DATA_SIZE/2- 1:0] teta_in, addr_angle_ext;
wire exc_sel0, exc_sel1;

wire [BUS_ADDR_MSB  :0] addr_bus_mem, addr_bus_rd, addr_bus_wr;
wire [ADDR_SIZE - 1 :0] addr_bank0, addr_bank1, addr_angle;

wire mem_rd, mem_wr;
wire [1:0] bank_sel;

wire order;

assign bus_rdy     = ((mem_rdy || bus_addr == CONFIG_REG) || !busy_n);

//mux to memory data bus
assign data_in_bk0 = (mem_src) ? bus_wr : x_butt_out;
assign data_in_bk1 = (mem_src) ? bus_wr : y_butt_out;

//mux to memory address bus
assign addr_bk0    = (mem_src) ? addr_bus_mem[BUS_ADDR_MSB - 1:0] : addr_bank0;
assign addr_bk1    = (mem_src) ? addr_bus_mem[BUS_ADDR_MSB - 1:0] : addr_bank1;

//Generation of k*teta
assign addr_angle_ext = {addr_angle,{DATA_SIZE/2 - ADDR_SIZE{1'b0}}};
assign teta_in = addr_angle_ext >> ADDR_SIZE - 1;

memory_bank 
#(
  .DATA_SIZE(DATA_SIZE),
  .ADDR_SIZE(ADDR_SIZE)
) 
  MEM_BANK0
(
  .data_out     ( data_out_bk0 ),
  .data_in      ( data_in_bk0  ),
  .addr         ( addr_bk0     ),
  .write_enable ( bank0_en     ),
  .clk          ( clk          )
);

memory_bank 
#(
  .DATA_SIZE(DATA_SIZE),
  .ADDR_SIZE(ADDR_SIZE)
) 
  MEM_BANK1
(
  .data_out     ( data_out_bk1 ),
  .data_in      ( data_in_bk1  ),
  .addr         ( addr_bk1     ),
  .write_enable ( bank1_en     ),
  .clk          ( clk          )
);

butterfly_unit 
#(
  .DATA_WIDTH( DATA_SIZE )
)
  BUTTERFLY
(
  .x_out    ( x_butt_out   ),
  .y_out    ( y_butt_out   ),
  .ready    ( butt_rdy     ),
  .x_in     ( data_out_bk0 ),
  .y_in     ( data_out_bk1 ),
  .teta_in  ( teta_in      ),
  .exc_sel0 ( exc_sel0     ),
  .exc_sel1 ( exc_sel1     ),
  .start    ( start_butt   ),
  .operation( mode         ),
  .clk      ( clk          ),
  .rst_n    ( rst_n        )
);

addr_unit 
#(
  .DFT_POINTS ( DFT_POINTS )
)
  ADDR_UNIT
(
  .addr_bank0 ( addr_bank0     ),
  .addr_bank1 ( addr_bank1     ),
  .addr_angle ( addr_angle     ),
  .ready      ( addr_unit_rdy  ),
  .exc_sel0   ( exc_sel0       ),
  .exc_sel1   ( exc_sel1       ),
  .enable     ( addr_unit_en   ),
  .clk        ( clk            ),
  .rst_n      ( rst_n          )
);

assign rd_mem_en = (read_en  && bus_addr == MEM_READ);
assign wr_mem_en = (write_en && bus_addr == MEM_WRITE); 

assign mem_rd   = (mem_rd_core || (rd_mem_en && busy_n));
assign mem_wr   = (mem_wr_core || (wr_mem_en && busy_n));
assign bank_sel = {busy_n, addr_bus_mem[BUS_ADDR_MSB]};

memory_controller MEM_CONTROLLER
(
  .bank0_en ( bank0_en ),
  .bank1_en ( bank1_en ),
  .mem_rdy  ( mem_rdy  ),
  .mem_rd   ( mem_rd   ),
  .mem_wr   ( mem_wr   ),
  .bank_sel ( bank_sel )
);

assign gen_read_en = (rd_mem_en && bus_rdy && busy_n && enable);

bus_addr_gen
#(
  .DFT_POINTS ( DFT_POINTS )
)
  GEN_READ
(
  .addr_bus ( addr_bus_rd    ),
  .enable   ( gen_read_en    ),
  .clear    ( start          ),
  .order    ( order          ),
  .clk      ( clk            ),
  .rst_n    ( rst_n          )
);

assign gen_write_en = (wr_mem_en && bus_rdy && busy_n);

bus_addr_gen
#(
  .DFT_POINTS ( DFT_POINTS )
)
  GEN_WRITE
(
  .addr_bus ( addr_bus_wr    ),
  .enable   ( gen_write_en   ),
  .clear    ( start          ),
  .order    ( order          ),
  .clk      ( clk            ),
  .rst_n    ( rst_n          )
);

//Address bus to initialization and reading of banks through the system bus
assign addr_bus_mem = (!write_en  && read_en) ? addr_bus_rd : addr_bus_wr;

//=============================================================================
// 1)  Configuration Bits
//=============================================================================
localparam CONFIG_SIZE = 5;

wire [CONFIG_SIZE -1:0] config_out, config_in;
wire cfg_reg_en;

assign interrupt = int_set & config_out[INT_MASK];//config_out[INT_BIT] & config_out[INT_MASK];
assign start     = config_out[START_BIT];
assign mode      = config_out[MODE_BIT];
assign order     = config_out[ORDER_BIT];

generate
  genvar i;
  for(i = 0; i < CONFIG_SIZE; i = i + 1)
    begin
      if(i == START_BIT)
        assign config_in[i] = config_src ? start_set : bus_wr[START_BIT];
      else
        if(i == INT_BIT)
          assign config_in[i] = int_set;
        else
            assign config_in[i] = config_src ? config_out[i] : bus_wr[i];
    end
endgenerate

assign cfg_reg_en = ((write_en && busy_n && (bus_addr == CONFIG_REG)) || config_en);
 
ff 
#(
  .DATA_WIDTH ( CONFIG_SIZE )
)
  REG_CONFIG
(
  .q     ( config_out ),
  .d     ( config_in  ),
  .en    ( cfg_reg_en ),
  .clk   ( clk        ),
  .rst_n ( rst_n      )
);

wire [DATA_SIZE - 1:0] bus_rd_in, mux_banks;

assign mux_banks = (addr_bus_mem[BUS_ADDR_MSB]) ? data_out_bk1 : data_out_bk0;
assign bus_rd_in = (bus_addr[1]) ? {{(DATA_SIZE-CONFIG_SIZE){1'b0}}, config_out} : mux_banks; 

ff 
#(
  .DATA_WIDTH ( DATA_SIZE )
)
  BUS_READ_REG
(
  .q     ( bus_rd     ),
  .d     ( bus_rd_in  ),
  .en    ( read_en    ),
  .clk   ( clk        ),
  .rst_n ( rst_n      )
);

endmodule

//-----------------------------------------------------------------------------
//                            MODULES DEFIITIONS
//-----------------------------------------------------------------------------

//=============================================================================
// 1)  Register Definition
//=============================================================================
module ff
#(
  parameter DATA_WIDTH  = 32,         //FF data width
  parameter RESET_VALUE = 0           //FF value after reset
)(
  //OUTPUTS
  output reg [DATA_WIDTH - 1:0] q,    //FF output
  //INPUTS
  input wire [DATA_WIDTH - 1:0] d,    //FF input
  input wire en,                      //enable writing in FF
  input wire clk,                     //clock
  input wire rst_n                    //Reset, asynchrounous and active low
);

always @(posedge clk, negedge rst_n)
  if(!rst_n)
    q <= RESET_VALUE;
  else if(en)
    q <= d;
      
endmodule